﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;

namespace mirror
{
    public class cRenderable
    {
        public enum eFlag
        {
            RENDERABLE_NONE = 0,
            RENDERABLE_SHADOWCASTER = 1,
            RENDERABLE_PLAYER_HULK = 2,
            RENDERABLE_PLAYER_IRONMAN = 4,
        };

        static int sUuid = 0;
        int mRenderId;
        int mFlags;

        public cRenderable() 
        {
            mFlags = 0;
            mRenderId = sUuid;
            sUuid += 1;
        }

        public int RenderId() { return mRenderId; }
        public bool HasFlag(eFlag f) { return ((int)f & mFlags) != 0; }
        public void SetFlag(eFlag f) { mFlags |= (int)f; }


        public virtual void Render() {}

    }

    public class cQuadRenderable
    {
        VertexBuffer mVB;
        IndexBuffer mIB;
        VertexDeclaration mVertDecl;
        GraphicsDevice mGfxdevice;

        static int MAX_QUADS = 10;

        public void Initialize(GraphicsDevice gfxdevice)
        {
            VertexElement[] vert_elems = {
            new VertexElement(0, VertexElementFormat.Vector4,
                                    VertexElementUsage.Position, 0)};
            mVertDecl = new VertexDeclaration(vert_elems);

            mVB = new VertexBuffer(gfxdevice, mVertDecl, MAX_QUADS * 4 * 16, BufferUsage.None);
            mIB = new IndexBuffer(gfxdevice, IndexElementSize.SixteenBits, MAX_QUADS * 6 * 4, BufferUsage.None);

            mGfxdevice = gfxdevice;
            Vector4[] data = new Vector4[4 * MAX_QUADS];
            short[] indices = new short[MAX_QUADS * 6];
            for (int i = 0; i < MAX_QUADS; ++i)
            {
                short i4 = (short)(i * 4);
                data[i4 + 0] = new Vector4(0, 0, 0, i);
                data[i4 + 1] = new Vector4(0, 0, 1, i);
                data[i4 + 2] = new Vector4(1, 0, 1, i);
                data[i4 + 3] = new Vector4(1, 0, 0, i);

                int i6 = i * 6;
                indices[i6 + 0] = i4;
                indices[i6 + 1] = (short)(i4 + 1);
                indices[i6 + 2] = (short)(i4 + 2);
                indices[i6 + 3] = (short)(i4 + 2);
                indices[i6 + 4] = (short)(i4 + 3);
                indices[i6 + 5] = i4;
            }
           
            mVB.SetData(data);
            mIB.SetData(indices);
            
        }

        public void Render(Effect fx, int numQuads)
        {
            Debug.Assert(numQuads < MAX_QUADS);

            mGfxdevice.SetVertexBuffer(mVB);
            mGfxdevice.Indices = mIB;

            foreach (EffectPass pass in fx.CurrentTechnique.Passes)
            {
                pass.Apply();
                // we can draw them all in a single call.
                mGfxdevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,numQuads*4,0,numQuads*2);
            }
            mGfxdevice.SetVertexBuffer(null);
        }
    }

    // This handles all the rendering for us
    class cRenderManager : Singleton<cRenderManager>
    {   
#region  Fields
        GraphicsDevice mGfxDevice;
        ContentManager mContent;
        System.Collections.Generic.Dictionary<int,cRigidRenderable> mRigids;
        System.Collections.Generic.Dictionary<int, cLocalFxRenderable> mLocalFx;

        Shadow mShadowFx;

        List<cThudPostProc> mThudEffects = new List<cThudPostProc>();
        cBlitPostProc mBlitFx;

        cContrastPostProc mContrastPostProc;
        cColorFilterPostProc mColorFilterPostProc;

        RenderTarget2D[] mRT;
        RenderTarget2D mShadowRT;

        cQuadRenderable mQuad;

        Effect mGroundFx;

        SkySphere mSky;

        int mCurBoundRT;
#endregion

        public GraphicsDevice Device() { return mGfxDevice; }
        public ContentManager Content() { return mContent; }

        public void StartThud(Vector3 pos, float intensity, float radius, float duration)
        {
            cThudPostProc thud = new cThudPostProc();
            thud.LoadContent(mContent, mGfxDevice);
            thud.Start(pos,intensity,radius,duration);
            mThudEffects.Add(thud);
        }

        #region Initialize
        public cRenderManager()
        {
            mRigids = new System.Collections.Generic.Dictionary<int, cRigidRenderable>();
            mLocalFx = new System.Collections.Generic.Dictionary<int, cLocalFxRenderable>();
            mQuad = new cQuadRenderable();

            mShadowFx = new Shadow();

            mSky = new SkySphere();
        }

        public void Initialize(ContentManager content,GraphicsDevice device)
        {
            mContent = content;
            mGfxDevice = device;
            mQuad.Initialize(device);

            PresentationParameters pp = device.PresentationParameters;
            mRT = new RenderTarget2D[2];
            mRT[0] = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, device.DisplayMode.Format, DepthFormat.Depth24);
            mRT[1] = new RenderTarget2D(device, pp.BackBufferWidth, pp.BackBufferHeight, false, device.DisplayMode.Format, DepthFormat.Depth24);

            mShadowRT = new RenderTarget2D(device, 256, 256, false, SurfaceFormat.Vector4, DepthFormat.None);

            // Load blit

            mBlitFx = new cBlitPostProc();
            mBlitFx.LoadContent(mContent, mGfxDevice);

            // Shadow
            mShadowFx.Initialize();

            // Ground
            mGroundFx = mContent.Load<Effect>("Ground");

            // Sky
            mSky.Init();

            //Contrast
            mContrastPostProc = new cContrastPostProc();
            mContrastPostProc.LoadContent(mContent, mGfxDevice);

            mColorFilterPostProc = new cColorFilterPostProc();
            mColorFilterPostProc.LoadContent(mContent, mGfxDevice);
        }

        public void ActivateContrastPostProc(float duration)
        {
            mContrastPostProc.Start(0.8f, 2.0f,duration);
        }

        public void ActivateColorFilterPostProc(float redBlueLerp)
        {
            mColorFilterPostProc.Start(redBlueLerp, 0.25f);
        }

        public void RenderQuad(Effect fx, int numQuads) { mQuad.Render(fx,numQuads); }
        
        #endregion

        #region Update and Render

        public void Update(GameTime gametime)
        {
            // update local effects & remove dead ones
            List<cLocalFxRenderable> toRemove = new List<cLocalFxRenderable>();
            foreach (KeyValuePair<int, cLocalFxRenderable> kv in mLocalFx)
            {
                if(!kv.Value.Alive())
                {
                    toRemove.Add(kv.Value);
                }
            }
            foreach (cLocalFxRenderable lfx in toRemove)
            {
                RemoveLocalFx(lfx);
            }

            // Update thuds
            List<cThudPostProc> toRemoveThud = new List<cThudPostProc>();
            foreach (cThudPostProc thud in mThudEffects)
            {
                thud.Update(gametime);
                if(!thud.Alive())
                {
                    toRemoveThud.Add(thud);
                }
            }
            foreach (cThudPostProc thud in toRemoveThud)
            {
                mThudEffects.Remove(thud);
            }

            // Update contrast
            mContrastPostProc.Update(gametime);
            mColorFilterPostProc.Update(gametime);
        }

        public void Render()
        {
            mGfxDevice.SetRenderTarget(mRT[0]);
            
            mGfxDevice.Clear(Color.Black);
            mGfxDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            // 1. Render shadows into env texture
            int shadNum = 0;
            foreach( KeyValuePair<int,cRigidRenderable> kv in mRigids)
            {
                if (kv.Value.HasFlag(cRenderable.eFlag.RENDERABLE_SHADOWCASTER))
                {
                    bool isPlayer = true;
                    Vector3 v = kv.Value.GetTransform().Translation;
                    if (kv.Value.HasFlag(cRenderable.eFlag.RENDERABLE_PLAYER_HULK))
                        v.Y = 1.0f;
                    else if (kv.Value.HasFlag(cRenderable.eFlag.RENDERABLE_PLAYER_IRONMAN))
                        v.Y = 0.0f;
                    else
                    {
                        v.Y = 3.0f;
                        isPlayer = false;
                    }
                    mShadowFx.SetPositions(shadNum, v, isPlayer);
                    shadNum += 1;
                    if (shadNum >= Shadow.MAX_QUADS)
                        break;
                }
            }
            mShadowFx.SetObjNum(shadNum);


            //// Render ground
            //mGfxDevice.RenderState.AlphaBlendEnable = false;
            //mGfxDevice.RenderState.AlphaSourceBlend = Blend.One;
            //mGfxDevice.RenderState.AlphaDestinationBlend = Blend.InverseSourceAlpha;
            //mGfxDevice.RenderState.SourceBlend = Blend.One;
            //mGfxDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
            //mGfxDevice.RenderState.BlendFunction = BlendFunction.Add;
            //mGfxDevice.RenderState.DepthBufferWriteEnable = true;
            //PixHelper.BeginEvent(0xFFFFFFFF, "Rendering Ground");
            //mGroundFx.Parameters["Texture"].SetValue(mShadowRT.GetTexture());
            //mGroundFx.Parameters["View"].SetValue(Camera.Instance.ViewMatrix);
            //mGroundFx.Parameters["Projection"].SetValue(Camera.Instance.ProjectionMatrix);
            //RenderQuad(mGroundFx, 1);
            //PixHelper.EndEvent();

            // Render the quads using the instancing data

            // 2. Render rigids
            foreach (KeyValuePair<int, cRigidRenderable> kv in mRigids)
            {
                kv.Value.Render();
            }

            PixHelper.BeginEvent(0xFFFFFFFF, "Rendering Sky");
            //mSky.Render();
            PixHelper.EndEvent();

            mShadowFx.Render();

            int source_rt = 0;
            int target_rt = 1;
            foreach( cThudPostProc thud in mThudEffects)
            {
                PixHelper.BeginEvent(0xFFFFFFFF, "Rendering Thud");
                mGfxDevice.SetRenderTarget(mRT[target_rt]);
                thud.SetInput(mRT[source_rt]);
                thud.Update(null);
                thud.Render();
                source_rt = target_rt;
                target_rt = target_rt == 0 ? 1 : 0;
                PixHelper.EndEvent();
            }

            // Blit last one to backbuffer
            PixHelper.BeginEvent(0xFFFFFFFF, "Rendering Blit");
            mGfxDevice.SetRenderTarget(mRT[target_rt]);
            mBlitFx.SetInput(mRT[source_rt]);
            mBlitFx.Update(null);
            mBlitFx.Render();
            PixHelper.EndEvent();

            // 3. Render local fx
            foreach (KeyValuePair<int, cLocalFxRenderable> kv in mLocalFx)
            {
                PixHelper.BeginEvent(0xFFFFFFFF, "Rendering Local Effect");
                kv.Value.Render();
                PixHelper.EndEvent();
            }

            mCurBoundRT = target_rt;
        }

        public void RenderPostProc()
        {
            mGfxDevice.SetRenderTarget( null);
            /*
            if(mContrastPostProc.Alive())
            {
                PixHelper.BeginEvent(0xFFFFFFFF, "Rendering Contrast");
                mContrastPostProc.SetInput(mRT[mCurBoundRT]);
                mContrastPostProc.Update(null);
                mContrastPostProc.Render();
                PixHelper.EndEvent();
            }
            else
            {
                PixHelper.BeginEvent(0xFFFFFFFF, "Rendering Blit to backbuffer");
                mBlitFx.SetInput(mRT[mCurBoundRT]);
                mBlitFx.Update(null);
                mBlitFx.Render();
                PixHelper.EndEvent();
            }
             */
            PixHelper.BeginEvent(0xFFFFFFFF, "Rendering colorfilter to backbuffer");
            mColorFilterPostProc.SetInput(mRT[mCurBoundRT]);
            mColorFilterPostProc.Render();
            PixHelper.EndEvent();
        }
#endregion

#region
        // Local FX stuff
        public void AddLocalFx(cLocalFxRenderable fx)
        {
            mLocalFx[fx.RenderId()] = fx;
        }

        public void RemoveLocalFx(cLocalFxRenderable fx)
        {
            mLocalFx.Remove(fx.RenderId());
        }

        // Rigid Renderable stuff
        public void AddRigid(cRigidRenderable rigid)
        {
            mRigids[rigid.RenderId()] = rigid;
        }

        public void RemoveRigid(cRigidRenderable rigid)
        {
            mRigids.Remove(rigid.RenderId());
        }

        // Post Processing shit / BabisTODO
        public void EnablePostProc(int enum_or_something, bool val){}

        public RenderTarget2D GetRT(int index) { return mRT[index]; }

#endregion
    }
}